package org.calendar;

/*
 * JDateSelector.java
 *
 * Created 12-Mar-2010
 * 
 */


import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;
import javax.swing.JPopupMenu;
import javax.swing.ImageIcon;
import javax.swing.UIManager;
import javax.swing.BoxLayout;
import javax.swing.Box;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import java.net.URL ;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import java.text.SimpleDateFormat;
import java.text.DateFormatSymbols;
import javax.swing.text.DateFormatter;
import javax.swing.text.DefaultFormatterFactory;

import org.calendar.event.CalendarChangedEvent;
import org.calendar.event.CalendarListener;



/**
 * A component to select a date with a spinner and a calendar popup.
 * <p>
 * We can choose to have a spinner of a simple field that will
 * display the chosen date.
 * <p>
 * The format of the displayed date can be changed by invoking this
 * component method <code>setFormat</code>.
 * <p>
 * We can retrieve the selection as a <code>Date</code> object with
 * the method <code>getSelectedDate</code> or as a <code>Calendar</code>
 * object <code>getSelectedCalendar</code>.
 *<p>
 * This component itself listen to the jcalendar changes. To add to the
 * jcalendar other listener, uses the method that gets the jcalendar
 * then add listener.
 *
 * 
 * @author Julie Duvillier
 */

public class JDateSelector
                extends JPanel
                implements CalendarListener,
                ActionListener,
                ChangeListener{


  /** The jcalendar panel */
  private JCalendar jCalendar;

  /** The button that will popup the jcalendar panel*/
  private JButton popupButton;
  /** Icon of the button */
  private ImageIcon buttonIcon;
  /** The pop-up */
  private JPopupMenu calendarPopup;


  javax.swing.border.Border bor;
  private java.awt.Dimension edDim;
  /** The spinner*/
  private JSpinner spinner;
  /** The editor */
  private JSpinner.DateEditor editor;
  /** The spinner model*/
  private SpinnerDateModel spinModel;

  /** Represents if the spinner is enabled or not*/
  private boolean enableSpin;
  
  /** The date format*/
  private SimpleDateFormat dateFormat;

  /** represents if the components been initialised*/
  private boolean init;


    /**
     * Construct a new Date selector that is initialised with
     * the default jcalendar, icon and date format.
     * The spinner will be shown.
     */
    public JDateSelector(){
        this( new JCalendar(),true,null,null );
    }

    /**
     * Construct a new Date selector with a chosen icon for
     * the button that pop-up the jcalendar.
     * @param icon desired icon for the button that pop-up the jcalendar.
     */
    public JDateSelector(ImageIcon icon){
        this( new JCalendar(),true,null,icon);
    }

    /**
     * Construct a new Date selector with the default jcalendar, icon
     * and date format, the spinner can be enable or not.
     * If the spinner is disable only the formatted field
     * will be show.
     * @param enableSpin  <code>true</code> to enable the spinner,
     *                    <code>false</code> to disable it.
     */
    public JDateSelector(boolean enableSpin){
        this( new JCalendar(),enableSpin,null,null);
    }

     /**
     * Construct a new Date selector with a spinner enabled or not,
     * a chosen date format and icon for the pop-up button.
     * @param enableSpin   <code>true</code> to enable the spinner,
     *                     <code>false</code> to disable it.
     * @param dateFormat   the format to apply to dates.
     */
    public JDateSelector(  boolean enableSpin,
                           SimpleDateFormat dateFormat){
           this(  new JCalendar(),enableSpin, dateFormat,null );
    }

    /**
     * Construct a new Date selector with a spinner enabled or not,
     * a chosen date format and icon for the pop-up button.
     * @param enableSpin   <code>true</code> to enable the spinner,
     *                     <code>false</code> to disable it.
     * @param dateFormat   the format to apply to dates.
     * @param icon         desired icon for the button that pop-up the jcalendar.
     */
    public JDateSelector(   boolean enableSpin,
                            SimpleDateFormat dateFormat,
                            ImageIcon icon ){
        this( new JCalendar(),enableSpin, dateFormat, icon);
    }

    /**
     * Construct a new Date selector with a given jcalendar, spinner enabled or not,
     * a chosen date format and icon for the pop-up button.
     * @param jCalendar    desired jcalendar as pop-up
     * @param enableSpin   <code>true</code> to enable the spinner,
     *                     <code>false</code> to disable it.
     * @param dateFormat   the format to apply to dates.
     * @param icon         desired icon for the button that pop-up the jcalendar.
     */
    public JDateSelector(   JCalendar jCalendar,
                            boolean enableSpin,
                            SimpleDateFormat dateFormat,
                            ImageIcon icon) {

        this.jCalendar = jCalendar;
        this.jCalendar.addCalendarListener(this);

        if (dateFormat == null) {
            this.dateFormat = new SimpleDateFormat("d MMMM yyyy");
        } else {
            this.dateFormat = dateFormat;
        }

        this.dateFormat.setDateFormatSymbols(
                                new DateFormatSymbols( jCalendar.getLocale()) );

        
        initComponents(enableSpin,icon) ;
    }

    /** Initialise the Date Selector components. */
    private void initComponents( boolean enableSpin,ImageIcon icon ) {
        /* Create the calendar popup */
        calendarPopup = new JPopupMenu();
        calendarPopup.add(jCalendar);
        calendarPopup.setVisible(false);

        /* Create the spinner Elements
         *  the model
         *  the spinner itself and set his editor
         *  the editor end set his format
         */
        spinModel = new SpinnerDateModel();
        spinModel.addChangeListener(this);

        spinner = new JSpinner( spinModel );
        editor = new JSpinner.DateEditor(spinner);
        editor.getTextField().setEditable(false);
        editor.getTextField().setColumns(15);
        
        setFormat(dateFormat);

      
        edDim = editor.getPreferredSize();
        bor = editor.getBorder();

        /* Create the button that will popup the jcalendar*/
        popupButton = new JButton(  buttonIcon );
        popupButton.setMargin( new Insets(1,1,1,1) );
        popupButton.setFocusPainted(false);
        popupButton.addActionListener(this);

        // Add components to the panel.
        setButtonIcon(icon);
        setEnableSpin(enableSpin);
        init = true;
    }


    //Implements changeListener
    /**
     * Listens for changes in the date spinner.
     * <p>
     * Synchronise the calendar to display and
     * the selected date of the JCalendar with the
     * spinner date.
     * @param e the ChangeEvent.
     */
    public void stateChanged(ChangeEvent e) {
        jCalendar.setDate(spinModel.getDate());
        jCalendar.setSelectedDate(spinModel.getDate());   
    }

    //Implements CalendarListener
    /**
     * Listens for change in the jCalendar.
     * <p>
     * Synchronise the spinner model date with the selected
     * date in jcalendar.
     * If the pop-up is visible it closes it.
     * @param e the CalendarChangedEvent.
     */
    public void calendarChanged(CalendarChangedEvent e) {
        Date date =  e.getSelectedCalendar().getTime();
        if( date != spinModel.getDate() ){
            spinModel.setValue(  e.getSelectedCalendar().getTime() );
            if( calendarPopup.isVisible() ){
                calendarPopup.setVisible( false );
            }
        }
    }
    
    // Implements ActionListener
    /**
     * Invoked on the popupButton.
     * It show the calendar pop-up on click.
     * @param e the ActionEvent.
     */
    public void actionPerformed(ActionEvent e) {
        int x = popupButton.getWidth();
        int y = 0;
        calendarPopup.show(popupButton, x, y);
    }

    /**
     * Sets the format that dictates how the
     * dates values will be displayed.
     * @param format the simple date format to apply to dates.
     */
    public void setFormat(SimpleDateFormat format){
       DefaultFormatterFactory factory =
              (DefaultFormatterFactory) editor.getTextField().getFormatterFactory();
       DateFormatter formatter = (DateFormatter) factory.getDefaultFormatter();
       formatter.setFormat(format);
       editor.getTextField().setFormatterFactory(factory);
    }


    /**
     * Sets if the spinner is enabled or not.
     * <p>
     * If the spinner is enabled it will be show, otherwise
     * only the editor is shown.
     * @param b <code>true</code> to enable the spinner,
     *          <code>false</code> to disable it.
     */
    public void setEnableSpin( boolean b ){
        boolean oldEnableSpin =enableSpin;
        enableSpin = b ;

        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
        /*
         * Change Panel Components only if enablespin value changed
         * and components been initialized.
         */
        if( (oldEnableSpin != enableSpin) || ! init ) {
           removeAll();
           if( enableSpin ) {
               spinner.setPreferredSize(edDim);
               spinner.setEditor(editor);
               add(spinner);
                
           } else {
               if( init ) {
                   editor.setBorder( spinner.getBorder() );
               }
               add(editor);
           }
           add( Box.createHorizontalStrut(10));
           add(popupButton);
        }
    }

    /**
     * Sets the calendar pop-up button icon.
     * If icon is <code>null</code> it will use the default icon.
     * @param icon the ImageIcon.
     */
    public void setButtonIcon(  ImageIcon icon ){
        if (icon == null) {
            URL imgURL = getClass().getResource("images/calendarIcon.gif");
            buttonIcon = new ImageIcon(imgURL);
        } else {
            buttonIcon = icon;
        }
        popupButton.setIcon(buttonIcon);
        
    }

    /**
     * Sets the locale.
     * @param locale the value of the new locale
     */
    @Override
    public void setLocale(Locale locale){
        super.setLocale(locale);
        jCalendar.setLocale(locale);
        dateFormat.setDateFormatSymbols( new DateFormatSymbols(locale) );
        setFormat(dateFormat);
    }

    /**
     * Sets the number of columns for the TextField of the date spinner
     * @param columns  - the number of columns >= 0
     * @throws IllegalArgumentException
     *         -  if columns is less than 0
     */
    public void setTextFieldColumns(int columns)
            throws IllegalArgumentException {
        if( columns >= 0){
           editor.getTextField().setColumns(columns);
        } else {
           throw  new IllegalArgumentException( "columns less than zero.");
        }

    }

    /**
     * Gets the jcalendar components.
     * @return the jcalendar component used by the date selector.
     */
    public JCalendar getJCalendar(){
        return jCalendar;
    }

    /**
     * Gets the selected date as a <code>Date</deta> object.
     * @return  value of the <code>Date</deta> object.
     */
    public Date getSelectedDate(){
        return spinModel.getDate();
    }

    /**
     * Gets the selected calendar with the selected
     * year,month,date.
     * It is an alternative to <code> getSelectedDate</code> method
     * @return a copy of the selected calendar
     */
    public Calendar getSelectedCalendar(){
        Calendar cal = Calendar.getInstance();
        cal.setTime( getSelectedDate());
        return cal;
    }

    /**
     * Gets the format that dictates how the
     * dates values are displayed.
     * @return the simple date format that apply to dates.
     */
    public SimpleDateFormat getFormat(){
        return dateFormat;
    }



    public static void main(String[] args) {
        try{
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }catch(Exception x){
                System.err.format("Error: %s%n", x);
        }
        JFrame frame = new JFrame();
        frame.add(new JDateSelector() );
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}
